﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;

namespace IcqSharp
{
    
    public class TLV
    {
        public UInt16 Type;
        public UInt16 Len;
        public Byte[] Data;

        public TLV() { }
        public TLV(UInt16 type, UInt16 len, Byte[] data)
        {
            Type = type;
            Len = len;
            Data = data;
        }
    }

    public class TLVChain
    {
        private List<TLV> items;

        public UInt16 Length
        {
            get
            {
                return (UInt16)(items.Count * 4 + items.Sum(t => t.Len));
            }
        }

        public TLV Find(UInt16 type, UInt16 index)
        {
            var i = 0;
            foreach (var t in items)
            {
                if (t.Type == type) i++;
                if (i >= index) return t;
            }

            return null;
        }
        public void Add(TLV tlv, Boolean replace)
        {
            var exist = Find(tlv.Type, 1);
            if (exist != null && replace)
            {
                exist.Len = tlv.Len;
                exist.Data = tlv.Data;
                // return exist;
            }
            else
            {
                items.Add(tlv);
                // return tlv;
            }
        }
        public void Remove(TLV tlv)
        {
            items.Remove(tlv);

            //TLVChain list = this, prev = null, chain = this;

            //while (list != null)
            //{
            //    if (list.tlv == tlv)
            //    {
            //        if (prev != null) // relink
            //            prev.next = list.next;
            //        else if (list.next != null)
            //        { // move second item's tlv to the first item
            //            list.tlv = list.next.tlv;
            //            list = list.next;
            //        }
            //        else // result is an empty chain (NULL)
            //            chain = null;
            //        // release chain item memory
            //        list.tlv.Data = null;
            //        list = null;
            //    }
            //    prev = list;
            //    list = list.next;
            //}

            //return chain;
        }


        public ushort GetLength(ushort wType, ushort wIndex)
        {
            TLV tlv = Find(wType, wIndex);
            if (tlv != null) return tlv.Len;

            return 0;
        }

        public UInt32 GetDWord(ushort wType, ushort wIndex)
        {
            var dw = (UInt32)0;

            var tlv = Find(wType, wIndex);
            if (tlv != null && tlv.Len >= 4)
            {
                dw |= (UInt32)tlv.Data[0] << 24;
                dw |= (UInt32)tlv.Data[1] << 16;
                dw |= (UInt32)tlv.Data[2] << 8;
                dw |= (UInt32)tlv.Data[3];
            }

            return dw;
        }
        public ushort GetWord(ushort wType, ushort wIndex)
        {
            ushort w = 0;

            TLV tlv = Find(wType, wIndex);
            if (tlv != null && tlv.Len >= 2)
            {
                w |= (ushort)((ushort)(tlv.Data[0]) << 8);
                w |= (ushort)tlv.Data[1];
            }

            return w;
        }
        public byte GetByte(ushort wType, ushort wIndex)
        {
            byte b = 0;

            TLV tlv = Find(wType, wIndex);
            if (tlv != null && tlv.Len > 0)
            {
                b = tlv.Data[0];
            }

            return b;
        }


        public int GetNumber(ushort wType, ushort wIndex)
        {
            TLV tlv = Find(wType, wIndex);

            if (tlv != null)
            {
                if (tlv.Len == 1)
                    return GetByte(wType, wIndex);
                else if (tlv.Len == 2)
                    return GetWord(wType, wIndex);
                else if (tlv.Len == 4)
                    return (int)GetDWord(wType, wIndex);
            }
            return 0;
        }


        //public double GetDouble(ushort wType, ushort wIndex)
        //{
        //    var tlv = GetTLV(wType, wIndex);

        //    if (tlv != null && tlv.Len == 8)
        //    {
        //        var stream = new MemoryStream(tlv.Data);
        //        return stream.ReadDouble();
        //    }
        //    return 0;
        //}

        public Byte[] GetData(UInt16 Type, UInt16 Index)
        {
            var tlv = Find(Type, Index);
            return (tlv == null) ? null : tlv.Data;
        }

        public String GetString(UInt16 Type, UInt16 Index)
        {
            var tlv = Find(Type, Index);
            return (tlv == null) ? null :
                tlv.Data.ToAsciiString();
        }


        public TLVChain() { }
        public TLVChain(Stream s, UInt16 count = 0)
        {
            items = new List<TLV>();

            while (s.Length - s.Position >= 4 && (count == 0 || items.Count < count))
            {
                var tlv = new TLV();

                tlv.Type = s.ReadUInt16();
                tlv.Len = s.ReadUInt16();

                //if (s.Available < tlv.Len) break;

                tlv.Data = s.ReadBytes(tlv.Len);

                items.Add(tlv);
            }
        }

    }

    // set maxTlvs<=0 to get all TLVs in length,
    // or a positive integer to get at most the first n
    //public TLVChain ReadIntoTLVChain(Stream s)
    //{
            

    //    TLVChain now, last = null, chain = null;
    //    ushort now_tlv_len;
    //    int len = wLen;

    //    //if (buf == null || wLen == 0) return null;

            

    //    while (len > 0) // don't use unsigned variable for this check
    //    {
    //        now = new TLVChain();

    //        now.tlv.Type = unpackWord(buf);
    //        now_tlv_len = unpackWord(buf);
    //        now.tlv.Len = now_tlv_len;
    //        len -= 4;

    //        if (now_tlv_len < 1)
    //        {
    //            now.tlv.Data = null;
    //        }
    //        else if (now_tlv_len <= len)
    //        {
    //            now.tlv.Data = new byte[now_tlv_len];
    //            if (now.tlv.Data != null)
    //                buf.Read(now.tlv.Data, 0, now_tlv_len);
    //        }
    //        else
    //        {
    //            // the packet is shorter than it should be
    //            now = null;
    //            return chain; // give at least the rest of chain
    //        }

    //        if (chain != null) // keep the original order
    //            last.next = now;
    //        else
    //            chain = now;

    //        last = now;

    //        len -= now_tlv_len;
    //        //*buf += now_tlv_len;

    //        if (--maxTlvs == 0)
    //            break;
    //    }

    //    return chain;
    //}

    //public void disposeChain(ref TLVChain chain)
    //{
    //    var now = chain;
    //    while (now != null)
    //    {
    //        var next = now.next;

    //        now.tlv.Data = null;
    //    }

    //    chain = null;
    //}



    
}
